package org.murphy.mock_spring.implement;

import org.murphy.mock_spring.annotations.MockComponent;
import org.murphy.mock_spring.annotations.MockResource;
import org.murphy.mock_spring.interfaces.Container;
import org.murphy.mock_spring.interfaces.InitContainer;
import org.reflections.Reflections;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Set;

/**
 * @program: murphy_mock
 * @description: Default init container function,
 * @author: 45048254 Murphy C Wang
 * @create: 2019-06-20 15:03
 **/
public class DefaultInitContainer implements InitContainer {
    private static DefaultInitContainer initContainer;

    private DefaultInitContainer() {
    }

    /**
     * Instance function, Singleton pattern
     * @return this class that instanced
     */
    public static InitContainer instance() {
        if (initContainer == null) {
            synchronized (DefaultContainer.class) {
                if (initContainer == null) {
                    initContainer = new DefaultInitContainer();
                }
            }
        }
        return initContainer;
    }

    private Container container = DefaultContainer.instanse();

    @Override
    public void init(Object self, String packagePath) throws IllegalAccessException, InstantiationException {
        if (self == null) {
            throw new IllegalArgumentException("self must not null");
        } else {
            if (container.getBean(self.getClass().getName()) == null) {
                container.addBean(self.getClass().getName(), self);
            }
        }
        if (packagePath.isEmpty()) {
            throw new IllegalArgumentException("Container.java packagePath is null");
        }
        autoInstanceField(self);
        initAllInstance(packagePath);
    }

    @Override
    public void initAllInstance(String packagePath) throws IllegalAccessException, InstantiationException {
        Reflections reflections = new Reflections(packagePath);
        Set<Class<?>> classes = reflections.getTypesAnnotatedWith(MockComponent.class);
        for (Class clazz : classes) {
            String className = clazz.getTypeName();
            Object classObj = container.getBean(className);
            if (classObj == null) {
                classObj=clazz.newInstance();
            }
            autoInstanceField(classObj);
        }
    }

    /**
     * Auto instance object's field, and field's field
     * @param targetObj
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    private void autoInstanceField(Object targetObj) throws IllegalAccessException, InstantiationException{
        Class targetClass = targetObj.getClass();
        Field[] fields = targetClass.getDeclaredFields();
        if (fields.length == 0) {
            return;
        }
        for (Field field : fields) {
            Annotation annotation = field.getDeclaredAnnotation(MockResource.class);
            if (annotation != null) {
                Object bean = container.getBean(field.getName());
                Class fieldClass = field.getType();
                if (bean == null) {
                    bean = fieldClass.newInstance();
                    container.addBean(((MockResource) annotation).value().isEmpty() ? ((MockResource) annotation).value() : field.getName(), bean);
                }
                field.setAccessible(true);
                field.set(targetObj, bean);
                field.setAccessible(false);
                autoInstanceField(bean);
            }else{
                return;
            }
        }
    }
}
